package net.sourceforge.stripes.util.bean;

import net.sourceforge.stripes.test.TestActionBean;
import net.sourceforge.stripes.test.TestBean;
import net.sourceforge.stripes.test.TestEnum;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

/**
 * 
 */
public class PropertyExpressionEvaluationTests {
    @Test(groups="fast")
    public void testGetBasicPropertyType() {
        PropertyExpression expr = PropertyExpression.getExpression("singleLong");
        PropertyExpressionEvaluation eval = new PropertyExpressionEvaluation(expr, new TestActionBean());
        Class<?> type = eval.getType();
        Assert.assertEquals(type, Long.class);
    }

    @Test(groups="fast")
    public void testGetPropertyTypeWithPropertyAccess() {
        PropertyExpression expr = PropertyExpression.getExpression("publicLong");
        PropertyExpressionEvaluation eval = new PropertyExpressionEvaluation(expr, new TestActionBean());
        Class<?> type = eval.getType();
        Assert.assertEquals(type, Long.class);
    }

    @Test(groups="fast")
    public void testGetPropertyTypeForListOfLongs() {
        PropertyExpression expr = PropertyExpression.getExpression("listOfLongs[17]");
        PropertyExpressionEvaluation eval = new PropertyExpressionEvaluation(expr, new TestActionBean());
        Class<?> type = eval.getType();
        Assert.assertEquals(type, Long.class);
    }

    @Test(groups="fast")
    public void testGetPropertyTypeForReadThroughList() {
        PropertyExpression expr = PropertyExpression.getExpression("listOfBeans[3].enumProperty");
        PropertyExpressionEvaluation eval = new PropertyExpressionEvaluation(expr, new TestActionBean());
        Class<?> type = eval.getType();
        Assert.assertEquals(type, TestEnum.class);
    }

    @Test(groups="fast")
    public void testGetPropertyTypeWithBackToBackMapIndexing() {
        PropertyExpression expr = PropertyExpression.getExpression("nestedMap['foo']['bar']");
        PropertyExpressionEvaluation eval = new PropertyExpressionEvaluation(expr, new TestBean());
        Class<?> type = eval.getType();
        Assert.assertEquals(type, Boolean.class);
    }

    @Test(groups="fast")
    public void testGetPropertyTypeWithBackToBackListArrayIndexing() {
        PropertyExpression expr = PropertyExpression.getExpression("genericArray[1][0]");
        PropertyExpressionEvaluation eval = new PropertyExpressionEvaluation(expr, new TestBean());
        Class<?> type = eval.getType();
        Assert.assertEquals(type, Float.class);
    }

    //////////////////////////////////////////////////////////////////////////////////////

    @Test(groups="fast")
    public void testGetSimpleProperties() throws Exception {
        TestBean root = new TestBean();
        root.setStringProperty("testValue");
        root.setIntProperty(77);
        root.setLongProperty(777l);
        root.setEnumProperty(TestEnum.Seventh);

        Assert.assertEquals("testValue", BeanUtil.getPropertyValue("stringProperty", root));
        Assert.assertEquals(77, BeanUtil.getPropertyValue("intProperty", root));
        Assert.assertEquals(777l, BeanUtil.getPropertyValue("longProperty", root));
        Assert.assertEquals(TestEnum.Seventh, BeanUtil.getPropertyValue("enumProperty", root));
    }

    @Test(groups="fast")
    public void testGetNestedProperties() throws Exception {
        TestBean root = new TestBean();
        TestBean nested = new TestBean();
        root.setNestedBean(nested);
        nested.setStringProperty("testValue");
        nested.setIntProperty(77);
        nested.setLongProperty(777l);
        nested.setEnumProperty(TestEnum.Seventh);

        Assert.assertEquals("testValue", BeanUtil.getPropertyValue("nestedBean.stringProperty", root));
        Assert.assertEquals(77, BeanUtil.getPropertyValue("nestedBean.intProperty", root));
        Assert.assertEquals(777l, BeanUtil.getPropertyValue("nestedBean.longProperty", root));
        Assert.assertEquals(TestEnum.Seventh, BeanUtil.getPropertyValue("nestedBean.enumProperty", root));
    }

    @Test(groups="fast")
    public void testGetNullProperties() throws Exception {
        TestBean root = new TestBean();

        // Test simple and nested props, leaving out primitives
        Assert.assertNull( BeanUtil.getPropertyValue("stringProperty" , root) );
        Assert.assertNull( BeanUtil.getPropertyValue("longProperty" , root) );
        Assert.assertNull( BeanUtil.getPropertyValue("enumProperty" , root) );
        Assert.assertNull( BeanUtil.getPropertyValue("nestedBean.stringProperty" , root) );
        Assert.assertNull( BeanUtil.getPropertyValue("nestedBean.longProperty" , root) );
        Assert.assertNull( BeanUtil.getPropertyValue("nestedBean.enumProperty" , root) );

        Assert.assertNull( BeanUtil.getPropertyValue("stringArray", root));
        Assert.assertNull( BeanUtil.getPropertyValue("stringList[7]", root));
        Assert.assertNull( BeanUtil.getPropertyValue("stringList", root));
        Assert.assertNull( BeanUtil.getPropertyValue("stringMap['seven']", root));
        Assert.assertNull( BeanUtil.getPropertyValue("stringMap", root));
    }

    @Test(groups="fast")
    public void testSetThenGetSimpleProperties() throws Exception {
        TestBean root = new TestBean();

        BeanUtil.setPropertyValue("stringProperty", root, "testValue");
        BeanUtil.setPropertyValue("intProperty",    root, 77);
        BeanUtil.setPropertyValue("longProperty",   root, 777l);
        BeanUtil.setPropertyValue("enumProperty",   root, TestEnum.Seventh);

        Assert.assertEquals("testValue", root.getStringProperty());
        Assert.assertEquals("testValue", BeanUtil.getPropertyValue("stringProperty", root));

        Assert.assertEquals(77, root.getIntProperty());
        Assert.assertEquals(77, BeanUtil.getPropertyValue("intProperty", root));

        Assert.assertEquals(new Long(777l), root.getLongProperty());
        Assert.assertEquals(777l, BeanUtil.getPropertyValue("longProperty", root));

        Assert.assertEquals(TestEnum.Seventh, root.getEnumProperty());
        Assert.assertEquals(TestEnum.Seventh, BeanUtil.getPropertyValue("enumProperty", root));

    }

    @Test(groups="fast")
    public void testSetThenGetNestedProperties() throws Exception {
        TestBean root = new TestBean();

        BeanUtil.setPropertyValue("nestedBean.stringProperty", root, "testValue");
        BeanUtil.setPropertyValue("nestedBean.intProperty",    root, 77);
        BeanUtil.setPropertyValue("nestedBean.longProperty",   root, 777l);
        BeanUtil.setPropertyValue("nestedBean.enumProperty",   root, TestEnum.Seventh);

        Assert.assertEquals("testValue", root.getNestedBean().getStringProperty());
        Assert.assertEquals("testValue", BeanUtil.getPropertyValue("nestedBean.stringProperty", root));

        Assert.assertEquals(77, root.getNestedBean().getIntProperty());
        Assert.assertEquals(77, BeanUtil.getPropertyValue("nestedBean.intProperty", root));

        Assert.assertEquals(new Long(777l), root.getNestedBean().getLongProperty());
        Assert.assertEquals(777l, BeanUtil.getPropertyValue("nestedBean.longProperty", root));

        Assert.assertEquals(TestEnum.Seventh, root.getNestedBean().getEnumProperty());
        Assert.assertEquals(TestEnum.Seventh, BeanUtil.getPropertyValue("nestedBean.enumProperty", root));

        Assert.assertTrue(root.getNestedBean() == BeanUtil.getPropertyValue("nestedBean", root));
    }

    @Test(groups="fast")
    public void testListProperties() throws Exception {
        TestBean root = new TestBean();

        BeanUtil.setPropertyValue("beanList[3]", root, new TestBean());
        Assert.assertNull( BeanUtil.getPropertyValue("beanList[0]", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanList[1]", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanList[2]", root) );
        Assert.assertNotNull( BeanUtil.getPropertyValue("beanList[3]", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanList[4]", root) );

        BeanUtil.setPropertyValue("beanList[3].stringProperty", root, "testValue");
        Assert.assertEquals("testValue", root.getBeanList().get(3).getStringProperty());

        BeanUtil.setPropertyValue("stringList[1]", root, "testValue");
        BeanUtil.setPropertyValue("stringList[5]", root, "testValue");
        BeanUtil.setPropertyValue("stringList[9]", root, "testValue");
        BeanUtil.setPropertyValue("stringList[7]", root, "testValue");
        BeanUtil.setPropertyValue("stringList[3]", root, "testValue");

        for (int i=0; i<10; ++i) {
            if (i%2 == 0) {
                Assert.assertNull(root.getStringList().get(i),
                                  "String list index " + i + " should be null.");
            }
            else {
                Assert.assertEquals("testValue", root.getStringList().get(i),
                                    "String list index " + i + " should be 'testValue'.");
            }
        }
    }

    @Test(groups="fast")
    public void testMapProperties() throws Exception {
        TestBean root = new TestBean();

        Assert.assertNull( BeanUtil.getPropertyValue("stringMap['foo']", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("stringMap['bar']", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("stringMap['testValue']", root) );

        Assert.assertNull( BeanUtil.getPropertyValue("beanMap['foo']", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanMap['bar']", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanMap['testValue']", root) );

        Assert.assertNull( BeanUtil.getPropertyValue("beanMap['foo'].longProperty", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanMap['bar'].stringProperty", root) );
        Assert.assertNull( BeanUtil.getPropertyValue("beanMap['testValue'].enumProperty", root) );

        BeanUtil.setPropertyValue("stringMap['testKey']", root, "testValue");
        Assert.assertEquals("testValue", root.getStringMap().get("testKey"));

        BeanUtil.setPropertyValue("beanMap['testKey'].enumProperty", root, TestEnum.Fifth);
        Assert.assertNotNull(root.getBeanMap());
        Assert.assertNotNull(root.getBeanMap().get("testKey"));
        Assert.assertEquals(TestEnum.Fifth, root.getBeanMap().get("testKey").getEnumProperty());
    }

    @Test(groups="fast")
    public void testSetNull() throws Exception {
        TestBean root = new TestBean();

        // Try setting a null on a null nested property and make sure the nested
        // property doesn't get instantiated
        Assert.assertNull(root.getNestedBean());
        BeanUtil.setPropertyToNull("nestedBean.stringProperty", root);
        Assert.assertNull(root.getNestedBean());

        // Now set the property set the nest bean and do it again
        root.setNestedBean( new TestBean() );
        BeanUtil.setPropertyToNull("nestedBean.stringProperty", root);
        Assert.assertNotNull(root.getNestedBean());
        Assert.assertNull(root.getNestedBean().getStringProperty());

        // Now set the string property and null it out for real
        root.getNestedBean().setStringProperty("Definitely Not Null");
        BeanUtil.setPropertyToNull("nestedBean.stringProperty", root);
        Assert.assertNotNull(root.getNestedBean());
        Assert.assertNull(root.getNestedBean().getStringProperty());

        // Now use setNullValue to trim the nestedBean
        BeanUtil.setPropertyToNull("nestedBean", root);
        Assert.assertNull(root.getNestedBean());

        // Now try some nulling out of indexed properties
        root.setStringList(new ArrayList<String>());
        root.getStringList().add("foo");
        root.getStringList().add("bar");
        Assert.assertNotNull(root.getStringList().get(0));
        Assert.assertNotNull(root.getStringList().get(1));
        BeanUtil.setPropertyToNull("stringList[1]", root);
        Assert.assertNotNull(root.getStringList().get(0));
        Assert.assertNull(root.getStringList().get(1));
    }

    @Test(groups="fast")
    public void testSetNullPrimitives() throws Exception {
        TestBean root = new TestBean();
        root.setBooleanProperty(true);
        root.setIntProperty(77);

        Assert.assertEquals(77, root.getIntProperty());
        Assert.assertEquals(true, root.isBooleanProperty());

        BeanUtil.setPropertyToNull("intProperty", root);
        BeanUtil.setPropertyToNull("booleanProperty", root);

        Assert.assertEquals(0, root.getIntProperty());
        Assert.assertEquals(false, root.isBooleanProperty());
    }

    /**
     * Tests a bug whereby the Introspector/PropertyDescriptor returns inaccessible
     * methods for getKey() and getValue() on all the JDK implementations of Map. A general
     * fix has been implmented to work up the chain and find an accessible method to invoke.
     */
    @Test(groups="fast")
    public void testMapEntryPropertyDescriptorBug() throws Exception {
        Map<String,String> map = new HashMap<String,String>();
        map.put("key", "value");
        Map.Entry<String,String> entry = map.entrySet().iterator().next();
        String value = (String) BeanUtil.getPropertyValue("value", entry);
        Assert.assertEquals(value, "value");
    }

    /**
     * Fix for a problem whereby in certain circumstances the PropertyExpressionEvaluation
     * would not fall back to looking at instance information to figure out the type info.
     */
    @Test(groups="fast")
    public void testFallBackToInstanceInfo() throws Exception {
        Map<String,TestBean> map = new HashMap<String,TestBean>();
        map.put("foo", new TestBean());
        map.get("foo").setStringProperty("bar");
        Map.Entry<String,TestBean> entry = map.entrySet().iterator().next();
        String value = (String) BeanUtil.getPropertyValue("value.stringProperty", entry);
        Assert.assertEquals(value, "bar");
    }

    /** Following classes are part of an inheritance torture test! */
    public static class Wombat { public String getName() { return "Wombat"; } }
    public static class SubWombat extends Wombat { @Override public String getName() { return "SubWombat"; } }
    public static class Foo<P extends Wombat> {
        private P wombat;
        public P getWombat() { return this.wombat; }
        public void setWombat(P wombat) { this.wombat = wombat; }
    }
    public static class Owner {
        private Foo<SubWombat> foo;
        public Foo<SubWombat> getFoo() { return foo; }
        public void setFoo(final Foo<SubWombat> foo) { this.foo = foo; }
    }

    @Test(groups="fast")
    public void testGnarlyInheritanceAndGenerics() throws Exception {
        Owner owner = new Owner();
        Foo<SubWombat> foo = new Foo<SubWombat>();
        owner.setFoo(foo);
        foo.setWombat(new SubWombat());
        String value = (String) BeanUtil.getPropertyValue("foo.wombat.name", owner);
        Assert.assertEquals(value, "SubWombat");
    }


}
